/*
 * Copyright (c) Sven Fabricius, 2008 - GPL
 *
 * Based on RTL8186 SourceForge SD/MMC Mod Driver.
 *
 * Version 0.2
 *
 */


#include <linux/module.h>
#include <linux/init.h>		/* Needed for the macros */
#include <linux/kernel.h>	/* Needed for KERN_INFO */
#include <asm/io.h>
#include <linux/time.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/ctype.h> 

/*
 * *******************************************************************
 *
 *                This is the only configurable part.
 *
 * *******************************************************************
 *
 */


#define DEVICE_NAME "gpio"
#define DEVICE_NR(device) (MINOR(device))
#define DEVICE_ON(device)
#define DEVICE_OFF(device)


/* Let that include where it is or compilation fails on INIT_REQUEST/CURRENT */


MODULE_AUTHOR("S. Fabricius");
MODULE_DESCRIPTION("Driver for RTL GPIO pins");
MODULE_SUPPORTED_DEVICE("Edimax 7206APg");
MODULE_LICENSE("GPL");

/*
 * *******************************************************************
 *
 *               Do not change anything below !
 *
 * *******************************************************************
 *
 */


/* GPIO states */
#define RTL_GPIOC_DATA          0xBD010130
#define RTL_GPIOC_DIR           0xBD010134
#define RTL_GPIOC_IMR           0xBD010138

#define RTL_R32(addr)   (*(volatile unsigned long *)(addr))
#define RTL_W32(addr, l)   ((*(volatile unsigned long*)(addr)) = (l))

typedef unsigned int uint32;
typedef unsigned long u32_t;
typedef unsigned short u16_t;
typedef unsigned char u8_t;

static struct proc_dir_entry *proc_dir, *proc_GPCDATA, *proc_GPCDIR, *proc_GPCIMR;

// Converts a hexadecimal string to integer
int xtoi(const char* xs, u16_t* result)
{
 size_t szlen = strlen(xs);
 int i, xv, fact;

 if (szlen > 0)
 {
  // Converting more than 32bit hexadecimal value?
  if (szlen>8) return 2; // exit

  // Begin conversion here
  *result = 0;
  fact = 1;

  // Run until no more character to convert
  for(i=szlen-1; i>=0 ;i--)
  {
   if (isxdigit(*(xs+i)))
   {
    if (*(xs+i)>=97)
    {
     xv = ( *(xs+i) - 97) + 10;
    }
    else if ( *(xs+i) >= 65)
    {
     xv = (*(xs+i) - 65) + 10;
    }
    else
    {
     xv = *(xs+i) - 48;
    }
    *result += (xv * fact);
    fact *= 16;
   }
   else if (*(xs+i) == 0) 
    return 0;
   else
    return 4;
  }
  return 0;
 }

 // Nothing to convert
 return 1;
}

/*
 * *******************************************************************
 *
 *             Begin GPIO hardware access functions.
 *
 * *******************************************************************
 *
 */

static u16_t read_GPCDATA(void)
{
    return (RTL_R32(RTL_GPIOC_DATA) & 0xffff);
}

static u16_t read_GPCDIR(void)
{
    return (RTL_R32(RTL_GPIOC_DIR) & 0xffff);
}

static u16_t read_GPCIMR(void)
{
    return (RTL_R32(RTL_GPIOC_IMR) & 0xffff);
}

static int write_GPCDATA(u16_t value)
{
    u32_t old;
    old = RTL_R32(RTL_GPIOC_DATA) & 0xffff0000;
    RTL_W32(RTL_GPIOC_DATA, value | old);
    return(0);
}

static int write_GPCDIR(u16_t value)
{
    u32_t old;
    old = RTL_R32(RTL_GPIOC_DIR) & 0xffff0000;
    RTL_W32(RTL_GPIOC_DIR, value | old);
    return(0);
}

static int write_GPCIMR(u16_t value)
{
    u32_t old;
    old = RTL_R32(RTL_GPIOC_IMR) & 0xffff0000;
    RTL_W32(RTL_GPIOC_IMR, value | old);
    return(0);
}

/*
 * *******************************************************************
 *
 *             Begin PROC IO functions.
 *
 * *******************************************************************
 *
 */

static int proc_read_GPCDATA( char *buf, char **start, off_t offset, int size, int *eof, void *data)
{
    int bytes_written=0, ret;
    u16_t Data;

    Data=read_GPCDATA();

    ret=snprintf(buf+bytes_written,(size-bytes_written),"%04x\n",Data);
    bytes_written += (ret>(size-bytes_written)) ? (size-bytes_written):ret;

    *eof = 1;
    return bytes_written;
}

static int proc_read_GPCDIR( char *buf, char **start, off_t offset, int size, int *eof, void *data)
{
    int bytes_written=0, ret;
    u16_t Data;

    Data=read_GPCDIR();

    ret=snprintf(buf+bytes_written,(size-bytes_written),"%04x\n",Data);
    bytes_written += (ret>(size-bytes_written)) ? (size-bytes_written):ret;

    *eof = 1;
    return bytes_written;
}

static int proc_read_GPCIMR( char *buf, char **start, off_t offset, int size, int *eof, void *data)
{
    int bytes_written=0, ret;
    u16_t Data;

    Data=read_GPCIMR();

    ret=snprintf(buf+bytes_written,(size-bytes_written),"%04x\n",Data);
    bytes_written += (ret>(size-bytes_written)) ? (size-bytes_written):ret;

    *eof = 1;
    return bytes_written;
}

static int proc_write_GPCDATA( struct file *instanz, const char *userbuffer, unsigned long count, void *data )
{
    char kernel_buffer[5];
    u16_t nData;

    if (count > 4)
     {
      copy_from_user( kernel_buffer, userbuffer, 4 );
      kernel_buffer[4] = '\0';

      if (xtoi(kernel_buffer, &nData) == 0)
        write_GPCDATA(nData);
     }

    return count;
}

static int proc_write_GPCDIR( struct file *instanz, const char *userbuffer, unsigned long count, void *data )
{
    char kernel_buffer[5];
    u16_t nData;

    if (count > 4)
     {
      copy_from_user( kernel_buffer, userbuffer, 4 );
      kernel_buffer[4] = '\0';

      if (xtoi(kernel_buffer, &nData) == 0)
        write_GPCDIR(nData);
     }

    return count;
}

static int proc_write_GPCIMR( struct file *instanz, const char *userbuffer, unsigned long count, void *data )
{
    char kernel_buffer[5];
    u16_t nData;

    if (count > 4)
     {
      copy_from_user( kernel_buffer, userbuffer, 4 );
      kernel_buffer[4] = '\0';

      if (xtoi(kernel_buffer, &nData) == 0)
        write_GPCIMR(nData);
     }

    return count;
}

/*
 * *******************************************************************
 *
 *             Begin GPIO Init functions.
 *
 * *******************************************************************
 *
 */

static int gpio_hardware_init(void)
{

    printk("gpioc: GPIO init\n");

    write_GPCDIR(0xffff);
    write_GPCDATA(0x0000);

    printk("gpioc: GPIO init end\n");

    return 0;
}


static int gpio_init(void)
{
    int result;

    result = gpio_hardware_init();

    if (result != 0) {
    printk("gpio: error %d in gpio_hardware_init\n", result);
    return -1;
    }
    proc_dir = proc_mkdir( "gpio", NULL );
    proc_GPCDATA=create_proc_entry( "GPCDATA", S_IRUGO | S_IWUGO, proc_dir );
    proc_GPCDIR=create_proc_entry( "GPCDIR", S_IRUGO | S_IWUGO, proc_dir );
    proc_GPCIMR=create_proc_entry( "GPCIMR", S_IRUGO | S_IWUGO, proc_dir );
    if( proc_GPCDATA ) {
        proc_GPCDATA->read_proc = proc_read_GPCDATA;
	proc_GPCDATA->write_proc = proc_write_GPCDATA;
        proc_GPCDATA->data = NULL;
    }
    if( proc_GPCDIR ) {
        proc_GPCDIR->read_proc = proc_read_GPCDIR;
	proc_GPCDIR->write_proc = proc_write_GPCDIR;
        proc_GPCDIR->data = NULL;
    }
    if( proc_GPCIMR ) {
        proc_GPCIMR->read_proc = proc_read_GPCIMR;
	proc_GPCIMR->write_proc = proc_write_GPCIMR;
        proc_GPCIMR->data = NULL;
    }


    return 0;
}

static void gpio_exit(void)
{
    if( proc_GPCDATA ) remove_proc_entry( "GPCDATA", proc_dir );
    if( proc_GPCDIR ) remove_proc_entry( "GPCDIR", proc_dir );
    if( proc_GPCIMR ) remove_proc_entry( "GPCIMR", proc_dir );
    if( proc_dir )  remove_proc_entry( "gpio", NULL );
}

static int __init gpio_driver_init(void)
{
    gpio_init();
    return 0;
}

static void __exit gpio_driver_exit(void)
{
    gpio_exit();
}

module_init(gpio_driver_init);
module_exit(gpio_driver_exit);
